#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <pthread.h>
#include <unistd.h>
#include "udk.h"
#include "pullsdk.h"
#include "platform.h"
#include <jni.h>
#include "bluetooth_interface.h"

//comment out following line to enable stdio redirecting
//#define ENABLE_STDIO_REDIRECT

#define FIND_CLASS(var, className) \
        var = env->FindClass(className); \
        UDK_LOGFATAL_IF(! var, "Unable to find class " className); \
        var = jclass(env->NewGlobalRef(var));

#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \
        var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \
        UDK_LOGFATAL_IF(! var, "Unable to find static method " methodName);

#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \
        var = env->GetMethodID(clazz, methodName, methodDescriptor); \
        UDK_LOGFATAL_IF(! var, "Unable to find static method " methodName);

#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \
        var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \
        UDK_LOGFATAL_IF(! var, "Unable to find field " fieldName);

#define GET_STATIC_FIELD_ID(var, clazz, varName, sig) \
        var = env->GetStaticFieldID(clazz, varName, sig); \
        UDK_LOGFATAL_IF(! var, "Unable to find static field " varName);

static JavaVM * g_jvm;
/* bluetooth module need this variable, so make it global */
JNIEnv * g_env;
static const char *classPathName = "com/zkteco/android/pullsdk/PullSDK";
#define CHARSET_DEFAULT	0	//Defalut Charset/UTF8
#define CHARSET_GBK		1	//GBK
static jint g_nCharset = CHARSET_DEFAULT;

#if defined(__ANDROID__) && defined(ENABLE_BLUETOOTH)
struct gBTCommClassInfo gBluetoothCommuClassInfo;
#endif

#ifdef ENABLE_STDIO_REDIRECT
//the demo code that redirect stdout/stderr to android output
#ifndef LOG_TAG
#define LOG_TAG "UDK"
#endif
static int pfd[2];
static pthread_t thr;
static const char* tag=LOG_TAG;
static void * thread_func(void*){
	ssize_t rdsz;
	char buf[128];
	while((rdsz = read(pfd[0],buf,sizeof buf -1))>0){
		if(buf[rdsz-1]=='\n') buf[rdsz-1]='\0';//add null terminator
		__android_log_write(ANDROID_LOG_DEBUG,tag,buf);
	}
	return 0;
}
static int start_logger(const char* app_name){
	if(app_name)	tag = app_name;
	/*make stdout line-buffered and stderr unbuffered*/
	setvbuf(stdout,0,_IOLBF,0);
	setvbuf(stderr,0,_IONBF,0);
	/*create pipe*/
	pipe(pfd);
	dup2(pfd[1],1);
	dup2(pfd[1],2);

	/*spawn logging thread*/
	if(pthread_create(&thr,0,thread_func,0)==-1) return -1;
	pthread_detach(thr);
	return 0;
}
#endif
static struct {
    jclass clazz;
	jmethodID updatePullSdkListenerInfo;
}gPullSdkServiceClassInfo;

/*
static void PULLStatusCallback(void *user,int state)
{
	JNIEnv * env;
	int status;
	int handle = (int)user;
	status = g_jvm->GetEnv(&env,JNI_VERSION_1_4);
	if(status < 0){
        status = g_jvm->AttachCurrentThread(&env, NULL);
        if(status < 0) {
            UDK_LOGE("callback_handler: failed to attach "
                 "current thread");
            return;
        }
	}
	env->CallStaticVoidMethod(gPullSdkServiceClassInfo.clazz, 
	gPullSdkServiceClassInfo.updatePullSdkListenerInfo,
	handle,state);
	g_jvm->DetachCurrentThread();
	
}
*/

/*change Jstring to GBK or UTF-8*/
static char * Jstring2Str( JNIEnv * env, jstring jstr, jint nCharset)
{
    char * rtn = NULL;
	jclass clsstring = NULL;
	jmethodID mid = NULL;
	jbyteArray barr = NULL;
	jstring strencode = NULL;
	jbyte * ba = NULL;
	jsize alen = 0;
	
	do
	{
		clsstring = env->FindClass("java/lang/String");
		if (NULL == clsstring) break;
		if(CHARSET_DEFAULT == nCharset)
		{
			 strencode = env->NewStringUTF("UTF-8");
		}
		else
		{
			 strencode = env->NewStringUTF("GBK");
		}
		if (NULL == strencode) break;
		mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
		if (NULL == mid) break;
		barr = (jbyteArray)env->CallObjectMethod(jstr,mid,strencode);
		if (NULL == barr) break;
		alen = env->GetArrayLength(barr);
		if(alen > 0)
		{
			ba = env->GetByteArrayElements(barr,JNI_FALSE);
			if(NULL == ba) break;
		}
	}	
	while(0);
	
    if(NULL != ba)
    {
        rtn = (char*)malloc(alen+1); //new char[alen+1];
        memcpy(rtn,ba,alen);
        rtn[alen]=0;
		env->ReleaseByteArrayElements(barr,ba,0);
    }
	else
	{
		rtn = (char*)malloc(1);
		rtn[0] = 0x0;
	}
    
    return rtn;
}
   
void PULLSetCharset(JNIEnv* env, jobject thiz, jint nCharset)
{
	g_env = env;
	g_nCharset = nCharset;
	return;
}

jlong PULLConnect(JNIEnv* env, jobject thiz, jstring jparam)
{
	void *handle;
	char *param = NULL;
	param = Jstring2Str( env,jparam, g_nCharset);
	g_env = env;
	handle = PullSDKConnect(param);
	free(param);
	return (jlong)handle;
}

void PULLDisconnect(JNIEnv* env, jobject thiz, jlong Handle)
{
	g_env = env;
	PullSDKDisconnect((UDK_HANDLE)Handle);
}

jint PULLGetDeviceData(JNIEnv* env, jobject thiz, jlong Handle, jbyteArray jBuffer, 
                        jintArray jbuffersize, jstring jTableName, jstring jFieldNames, 
                        jstring jFilter, jstring jOptions)
{
	jint ret = 0;
	int len = 0;
	char *TableName = NULL;
	char *FieldNames = NULL;
	char *Filter = NULL;
	char *Options = NULL;	
	
	TableName = Jstring2Str(  env, jTableName, g_nCharset);
	FieldNames = Jstring2Str(  env, jFieldNames, g_nCharset);
	Filter = Jstring2Str(  env, jFilter, g_nCharset);
	Options = Jstring2Str(  env, jOptions, g_nCharset);
	
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
    int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);

	g_env = env;
	ret = PullSDKGetDeviceData((UDK_HANDLE)Handle, (char *)buffer, *buffersize, TableName, FieldNames, Filter, Options);

    *buffersize = strlen((char *)buffer);

	env->SetByteArrayRegion(jBuffer, 0, strlen((char *)buffer), buffer);
	env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);

	env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);

	free(TableName);
	free(FieldNames);
	free(Filter);
	free(Options);
	return ret;
}

jint PULLGetDeviceDataCount(JNIEnv* env, jobject thiz, jlong Handle, 
                            jstring jTableName, jstring jFilter, jstring jOptions)
{
	jint ret = 0;
	char *TableName = NULL;
	char *Filter = NULL;
	char *Options = NULL;	
	TableName =  Jstring2Str(  env, jTableName, g_nCharset);
	Filter =  Jstring2Str(  env, jFilter, g_nCharset);
	Options =  Jstring2Str(  env, jOptions, g_nCharset);
	
	g_env = env;	
	ret = PullSDKGetDeviceDataCount((UDK_HANDLE)Handle, TableName, Filter, Options);

	free( TableName);
	free( Filter);
	free( Options);
	return ret;
}

jint PULLControlDevice(JNIEnv* env, jobject thiz, jlong Handle, jlong OperationID, 
                        jlong Param1, jlong Param2, jlong Param3, jlong Param4, jstring jOptions)
{
	jint ret = 0;
	char *Options = NULL;	
	Options = Jstring2Str(  env, jOptions, g_nCharset);
	g_env = env;	
	
	ret = PullSDKControlDevice((UDK_HANDLE)Handle, OperationID, Param1, 
                                Param2, Param3, Param4, Options);	
	free(Options);	
	return ret;
}

jint PULLSetDeviceData(JNIEnv* env, jobject thiz, jlong Handle, 
                            jstring jTableName, jstring jDatas,	jstring jOptions)
{
	jint ret = 0;
	char *TableName = NULL;
	char *Datas = NULL;
	char *Options = NULL;	
	
	
	TableName = Jstring2Str(  env, jTableName, g_nCharset);
	Datas = Jstring2Str(  env, jDatas, g_nCharset);
	Options = Jstring2Str(  env, jOptions, g_nCharset);
	
	
	g_env = env;
	ret = PullSDKSetDeviceData((UDK_HANDLE)Handle, TableName, Datas, Options);

	free(TableName);
	free(Datas);
	free(Options);
	return ret;
}

jint PULLDeleteDeviceData(JNIEnv* env, jobject thiz, jlong Handle, 
                        jstring jTableName, jstring jDatas, jstring jOptions)
{
	jint ret = 0;
	char *TableName = NULL;
	char *Datas = NULL;
	char *Options = NULL;	
	
	TableName = Jstring2Str(  env, jTableName, g_nCharset);
	Datas = Jstring2Str(  env, jDatas, g_nCharset);
	Options = Jstring2Str(  env, jOptions, g_nCharset);

	g_env = env;
	ret = PullSDKDeleteDeviceData((UDK_HANDLE)Handle, TableName, Datas, Options);

	free(TableName);
	free(Datas);
	free(Options);
	return ret;
}

jint PULLGetDeviceParam(JNIEnv* env, jobject thiz, jlong Handle, 
                        jbyteArray jBuffer, jintArray jbuffersize, jstring jItems)
{
	jint ret = 0;
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
	char *Items = NULL;
	
	Items = Jstring2Str(  env, jItems, g_nCharset);
	
    int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);
	g_env = env;
	ret = PullSDKGetDeviceParam((UDK_HANDLE)Handle, (char *)buffer, *buffersize, Items);
	
    *buffersize = strlen((char *)buffer);
    env->SetByteArrayRegion(jBuffer, 0, *buffersize, (jbyte*)buffer);
    env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);
	free(Items);
	
	env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
	env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
	return ret;
}

jint PULLGetTableStruct(JNIEnv* env, jobject thiz, jlong Handle,
                        jbyteArray jBuffer, jintArray jbuffersize)
{
    jint ret = 0;
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
    int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);
    g_env = env;
	ret = PullSDKGetTableStruct((UDK_HANDLE)Handle, (char *)buffer, *buffersize);

    *buffersize = strlen((char *)buffer);
 	if(*buffersize > 0)
    	env->SetByteArrayRegion(jBuffer, 0, *buffersize, (jbyte*)buffer);
    env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);
	
	env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
    
    return ret;
}

jint PULLSetTableStruct(JNIEnv* env, jobject thiz, jlong Handle, jstring jtable_struct)
{
    int ret;
    char * table_struct = NULL;
	table_struct = Jstring2Str(  env, jtable_struct, g_nCharset);
	
	g_env = env;
	ret = PullSDKSetTableStruct((UDK_HANDLE)Handle, table_struct);
	free(table_struct);
    return ret;
}


jint PULLSetDeviceParam(JNIEnv* env, jobject thiz, jlong Handle, jstring jItemAndValues)
{
	jint ret = 0;
	char * ItemAndValues = NULL;
	ItemAndValues = Jstring2Str(  env, jItemAndValues, g_nCharset);
	
	g_env = env;
	ret = PullSDKSetDeviceParam((UDK_HANDLE)Handle, ItemAndValues);

	free(ItemAndValues);
	return ret;
}

jint PULLSearchDevice(JNIEnv* env, jobject thiz, jstring jCommType, jstring jaddress, jbyteArray jBuffer, jintArray BufferSize)
{
    jint ret = 0;
	char *addr = NULL;
    char *commtype =  NULL;
	addr = Jstring2Str(  env, jaddress, g_nCharset);
	commtype = Jstring2Str(  env, jCommType, g_nCharset);
		
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
    int *buffersize = env->GetIntArrayElements(BufferSize, NULL);
    g_env = env;
    ret = PullSDKSearchDevice(commtype, addr, (char *)buffer, buffersize);
	
	free(addr);
	free(commtype);
    env->SetByteArrayRegion(jBuffer, 0, *buffersize, (jbyte*)buffer);
    env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);

	env->SetIntArrayRegion(BufferSize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(BufferSize, buffersize, JNI_ABORT);
    return ret;
}

int PULLGetRTLog(JNIEnv* env, jobject thiz, jlong handle, jbyteArray jBuffer, jintArray BufferSize)
{
    jint ret = 0;
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
    int *buffersize = env->GetIntArrayElements(BufferSize, NULL);

    g_env = env;
    ret = PullSDKGetRTLog((UDK_HANDLE)handle, (char *)buffer, *buffersize);
    *buffersize = strlen((char *)buffer);

    env->SetByteArrayRegion(jBuffer, 0, strlen((char *)buffer), (jbyte*)buffer);
    env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);

	env->SetIntArrayRegion(BufferSize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(BufferSize, buffersize, JNI_ABORT);
    return ret;
}

jint PULLModifyIPAddress(JNIEnv* env, jobject thiz, jstring jCommType, jstring jaddress, jstring jbuffer)
{
    jint ret = 0;
    char *addr =  NULL;
    char *commtype = NULL;
    char *buffer =  NULL;
	addr = Jstring2Str(  env, jaddress, g_nCharset);
	commtype = Jstring2Str(  env, jCommType, g_nCharset);
	buffer = Jstring2Str(  env, jbuffer, g_nCharset);

    g_env = env;   
    ret = PullSDKModifyIPAddress(commtype, addr, buffer, strlen(buffer));
    
    free(addr);
	free(commtype);
	free(buffer);
    return ret;
}

jint PULLGetDeviceFileData(JNIEnv* env, jobject thiz, jlong handle, jbyteArray jBuffer, 
                            jintArray jbuffersize, jstring jFilename, jstring jOptions)
{
    jint ret = 0;
    jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
	char *Filename = NULL;
    char *options =  NULL;
	Filename = Jstring2Str(  env, jFilename, g_nCharset);
	options = Jstring2Str(  env, jOptions, g_nCharset);
		
    int *buffersize = env->GetIntArrayElements(jbuffersize, NULL);

    g_env = env;
    ret = PullSDKGetDeviceFileData((UDK_HANDLE)handle, (char *)buffer, buffersize, Filename, options);

    env->SetByteArrayRegion(jBuffer, 0, *buffersize, (jbyte*)buffer);
	env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);
	
	free(Filename);
	free(options);
    
	env->SetIntArrayRegion(jbuffersize, 0, 1, buffersize);
    env->ReleaseIntArrayElements(jbuffersize, buffersize, JNI_ABORT);
    return ret;
}

jint PULLSetDeviceFileData(JNIEnv* env, jobject thiz, jlong handle, jstring jFilename,  
                            jbyteArray jBuffer, jint BufferSize, jstring jOptions)
{
    jint ret = 0;
	jbyte *buffer = (jbyte*)env->GetByteArrayElements(jBuffer, NULL);
	char *Filename = NULL;
	char *Options = NULL;	
	
	Filename = Jstring2Str(  env, jFilename, g_nCharset);
	Options = Jstring2Str(  env, jOptions, g_nCharset);
	
    g_env = env;
    ret = PullSDKSetDeviceFileData((UDK_HANDLE)handle, Filename, (char *)buffer, BufferSize, Options);
    env->SetByteArrayRegion(jBuffer, 0, BufferSize, (jbyte*)buffer);

    env->ReleaseByteArrayElements(jBuffer, buffer, JNI_ABORT);
	free(Filename);
	free(Options);
    return ret;
}

jint PullLastError(JNIEnv* env, jobject thiz)
{
    g_env = env;
    return PullSDKLastError();
}

static JNINativeMethod methods[] = { 
	{"SetCharset",	"(I)V",
		(void*)PULLSetCharset },
    { "Connect",			"(Ljava/lang/String;)J", 														
        (void*)PULLConnect },
	{ "Disconnect",			"(J)V",																				
        (void*)PULLDisconnect }, 
	{ "GetDeviceData",		"(J[B[ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",	
        (void*)PULLGetDeviceData },
	{ "GetDeviceDataCount",	"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",						
        (void*)PULLGetDeviceDataCount }, 
	{ "ControlDevice",		"(JJJJJJLjava/lang/String;)I",													
        (void*)PULLControlDevice }, 
	{ "SetDeviceData",		"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",					
        (void*)PULLSetDeviceData },
	{ "DeleteDeviceData",	"(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",					
        (void*)PULLDeleteDeviceData }, 
	{ "GetDeviceParam",		"(J[B[ILjava/lang/String;)I",										
        (void*)PULLGetDeviceParam }, 
	{ "SetDeviceParam",		"(JLjava/lang/String;)I",																
        (void *)PULLSetDeviceParam },
	{ "SearchDevice",		"(Ljava/lang/String;Ljava/lang/String;[B[I)I",
        (void *)PULLSearchDevice },
	{ "GetRTLog",		"(J[B[I)I",
        (void *)PULLGetRTLog},
	{ "ModifyIPAddress",		"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
        (void *)PULLModifyIPAddress },
	{ "GetDeviceFileData",		"(J[B[ILjava/lang/String;Ljava/lang/String;)I",
        (void *)PULLGetDeviceFileData },
	{ "SetDeviceFileData",		"(JLjava/lang/String;[BILjava/lang/String;)I",
        (void *)PULLSetDeviceFileData },
	{ "PullLastError",		"()I",
        (void *)PullLastError },
    { "GetTableStruct", "(J[B[I)I",
        (void *)PULLGetTableStruct },
    { "SetTableStruct", "(JLjava/lang/String;)I",
        (void *)PULLSetTableStruct },
};

static int registerNativeMethods(JNIEnv* env, const char* className,
		JNINativeMethod* gMethods, int numMethods) 
{
	jclass clazz, cl_bt;
    jfieldID obj_fid;
    jobject obj;

	g_env = env;
	clazz = env->FindClass(className);
	if (clazz == NULL) {
        UDK_LOGD("class %s not found\n", className);
		return JNI_FALSE;
	}
	if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
		return JNI_FALSE;
	}

    //FIND_CLASS(gPullSdkServiceClassInfo.clazz, "com/zkteco/bluetooth/PullSDK");
    FIND_CLASS(cl_bt, "com/zkteco/android/bluetooth/ZKBluetoothFactory");
    if (cl_bt == NULL) {
        UDK_LOGD("bt class not found\n");
		return JNI_FALSE;
    }

    GET_STATIC_FIELD_ID(obj_fid, cl_bt, "mZKBluetoothManager", "Lcom/zkteco/android/bluetooth/ZKBluetoothManager;");
    if (obj_fid == NULL) {
        UDK_LOGD("static member not found\n");
        return JNI_FALSE;
    }
    obj = env->GetStaticObjectField(cl_bt, obj_fid);
    gBluetoothCommuClassInfo.object = (jobject)env->NewGlobalRef(obj);
    gBluetoothCommuClassInfo.clazz = env->GetObjectClass(obj);
    //gBluetoothCommuClassInfo.object = obj;

#if defined(__ANDROID__) && defined(ENABLE_BLUETOOTH)
    //FIND_CLASS(gBluetoothCommuClassInfo.clazz, "com/zkteco/bluetooth/BluetoothManager");
    //GET_STATIC_METHOD_ID(gBluetoothCommuClassInfo.BluetoothInit, gBluetoothCommuClassInfo.clazz,
    //        "BluetoothInit",
    //        "(Ljava/lang/String;)I");
    //GET_STATIC_METHOD_ID(gBluetoothCommuClassInfo.BluetoothFree, gBluetoothCommuClassInfo.clazz,
    //        "BluetoothFree",
    //        "()I");
    GET_METHOD_ID(gBluetoothCommuClassInfo.BluetoothWrite, gBluetoothCommuClassInfo.clazz,
            "write",
            "([BII)I");
    GET_METHOD_ID(gBluetoothCommuClassInfo.BluetoothRead, gBluetoothCommuClassInfo.clazz,
            "read",
            "([BII)I");
    //GET_STATIC_METHOD_ID(gBluetoothCommuClassInfo.BluetoothSelect, gBluetoothCommuClassInfo.clazz,
    //        "BluetoothSelect",
    //        "()I");
#endif

	#ifdef ENABLE_STDIO_REDIRECT
	start_logger(NULL);	
	#endif
	return JNI_TRUE;
}

static int registerNatives(JNIEnv* env) {
	if (!registerNativeMethods(env, classPathName, methods,
			sizeof(methods) / sizeof(methods[0]))) {
		return JNI_FALSE;
	}

	return JNI_TRUE;
}

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {

	jint result = -1;
	JNIEnv* env = NULL;
	
	g_jvm = vm;
	
	if (vm->GetEnv((void **)&env, JNI_VERSION_1_4) != JNI_OK) {
        UDK_LOGD("vm->GetEnv failed\n");
		goto bail;
	}

	if (registerNatives(env) != JNI_TRUE) {
        UDK_LOGD("register native failed\n");
		goto bail;
	}

	result = JNI_VERSION_1_4;

	bail: return result;
}

